package com.gw.youTubeAudioAPI.decode 
{
	import com.gw.youTubeAudioAPI.decode.bitstream.MP3toRaw;
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import com.gw.youTubeAudioAPI.decode.container.Decoder;
	
	/**
	 * this class attempts to read bytearray streams and output the audio data
	 * created from information in the  Adobe flv spec:
	 * http://www.adobe.com/devnet/flv/
	 * and this file for reference (processing format bytearray)
	 * http://svn2.assembla.com/svn/TMS_bitstream/bitstream/FLVreader.cpp
	 * these sources for decoding mp3 bitstream
	 * http://github.com/claus/as3icy (an as3 port of a java port of a c decoder used to stream icecasts)
	 * http://www.mp3-tech.org/ (mp3 frame header details)
	 * TODO:Optimise
	 * FIXED: LOL! I am reading the audio backwards because I am parsing it backwards!!!
	 */
	
	public class FLVtoRaw implements Decoder
	{
		//decoder for bytestream
		private var mp3Decoder:MP3toRaw = null;// new MP3toRaw();
		
		private var rawBuffer:ByteArray = new ByteArray();
		private var payload:ByteArray 	= new ByteArray();
		private var _flv:FLV_Wrapper;
		private var headerSize:uint = 0;
		
		private var backPointers:FLV_Index;//spider all back-pointers so we can read forwards (must be a better solution)
		
		//pointers into flv data
		public static const AUDIO_TAG_OFFSET:	int = 	SizeOf.UI8*4 + 
														SizeOf.UB;
		public static const HEADER_SIZE_OFFSET:	int = 	SizeOf.UI8*4 + 
														SizeOf.UB;
		public static const TAG_DATA_OFFSET:	int = 	SizeOf.UI8*2 + 
														SizeOf.UI24*3;
		//constants for decoding format
		private var channelmask:int = Math.pow(0,2);
		private var samplemask:	int = Math.pow(1,2);
		private var ratemask:	int = Math.pow(2,2) + Math.pow(3,2);
		private var codecmask:	int = Math.pow(4,2) + Math.pow(5,2) + Math.pow(6,2) + Math.pow(7,2);
		
		//lookups for decoding format codes
		private var rateLUT:Array = [5512.5,11025,22050,44100];
		private var chanLUT:Array = [1,2];
			
		public function FLVtoRaw(flv:ByteArray):void
		{
			//-init-----------------------------------
			_flv = new FLV_Wrapper(flv);
			var bytesOut:ByteArray = new ByteArray();
			bytesOut.endian = Endian.BIG_ENDIAN;
			
			/*-header---------------------------------
			UI8 	0x46	F
			UI8 	0x4C	L
			UI8		0x56	V
			UI8		0x01	Version
			UB[5]	0
			UB[1]	1 		AudioTags
			UB[1]	0
			UB[1]	1 		VideoTags
			UI32			HeaderSize
			*/
			/* TODO: parse header bytearray
			trace("parsing header...");

			_flv.position = AUDIO_TAG_OFFSET;
			if (_flv.readUB()) throw new Error("FlV has no audio stream"); //no audio
			*/
			_flv.position = HEADER_SIZE_OFFSET;
			headerSize = _flv.readUI32();
			if (!headerSize) throw new Error("FlV has no header data"); //no audio
			
			backPointers = new FLV_Index(_flv); //read backwards from end to index audio tag backpointers into array
		}
		private function decodeNextTag():Boolean
		{
			var backPointerPos:int = backPointers.getNextPointer();
			if ( !backPointerPos ) return false; //final pointer will read 0  
			
			/*-FLVTAG------------------------------
			UI8		8/9/18	Type (8) is Audio
			UI24			Datasize
			UI24			Timestamp of tag
			UI8				Extension of above
			UI24		0	StreamID
			DATA			Data payload see Type and below
			*/
			
			_flv.position = backPointerPos + SizeOf.UI8;//move ahead of first tag (audio)
			var payloadSize:uint = _flv.readUI24(); //read Datasize
			_flv.position = backPointerPos + TAG_DATA_OFFSET; //skip rest of tag
			
			/*-AUDIOTAG-------------------------
			UB[4]			Format (2 mp3) (0 PCM)
			UB[2]	3		SampleRate (AAC is 3)
			UB[1]	0/1		SampleSize (16bit for compressed)
			UB[1]	0/1		Mono or Stereo (AAC is Stereo)
			UI8				size of sound data (AAC is 10)
			*/
			
			var format:Object = extractFormat(_flv.readUI8());//8bit array
			//trace("channels:", format.ch, "rate:", format.r, "codec", format.c, "depth:", format.s); 
			if (format.c != 2)//is not mp3
				throw new Error("the audio content of this flv is not mp3 and cannot be decoded (probably an flv2)");
			if (!mp3Decoder) mp3Decoder = new MP3toRaw(rateLUT[format.r], 2);//chanLUT[format.ch]); //joint stereo will appear as mono
			payload.clear();//reuse payload bytearray
			_flv.readBytes(payload, 0, payloadSize);
			return true;
		}
		//utility function ---------------------------------
		private function extractFormat(bytes:uint):Object {
			
			var channel:int = channelmask 	& bytes;
			var sample:	int = (samplemask 	& bytes) >> 1;
			var rate:	int = (ratemask 	& bytes) >> 2;
			var codec:	int = (codecmask 	& bytes) >> 4;
		
			return {ch:channel, s:sample, r:rate, c:codec};
		}
		//interface - read by audioStream-------------------
		public function extract(target:ByteArray, length:Number, startPosition:Number = -1):Number 
		{
			//target format = samples at 44,100 L | R 32Bit floats
			var attempts:int = 0;
			length <<= 3; //length is in samples (FLT32) 3 ^ 2 = 8 * 4bytes = 32bit per sample
			rawBuffer.clear();//reuse rawBuffer
			
			do
			{
				if (decodeNextTag()) 
				{
					//decode bytestream cpu == :'(
					mp3Decoder.decodeBitStream(payload, rawBuffer);
					attempts = 0;
				}
				else
				{
					if(++attempts > 100)//100 consecutive read fails == rawBuffer underflow probably EOF
						throw (new Error("error reading file: "+_flv.position));
				}
			}
			while (rawBuffer.length < length)//read data to the nearest packet (allow buffer over-run)
			
			//write requested amount (rounded to the nearest flvTag boundary) from rawBuffer to target
			target.writeBytes(rawBuffer, 0, length);
			return target.length >> 3; //remember! 1 sample = 32bits
		}
	}
}
